﻿using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using Dsl.Ped.Common.Converters;
using Dsl.Ped.Framework.Extensions;
using System.Linq.Expressions;
using Dsl.Ped.Framework.Common;
using Dsl.Ped.Framework.Projections.ValueProjections;
using Dsl.Ped.Framework.WPFControls.NumericTextBox;

namespace Dsl.Ped.Framework.Projections
{
    /// <summary>
    /// Factory for the creation of value-based projections
    /// </summary>
    public class ValueProjectionFactory : ProjectionFactoryBase
    {
        /// <summary>
        /// Initialize the projection control of the Value-based projection (TextBox, Label, ...)
        /// </summary>
        /// <typeparam name="T">Type of the projection (like TextBox, Label, etc.)</typeparam>
        /// <param name="projectionHint">Hint for the creation of a projection</param>
        /// <param name="source">Source object of the projection</param>
        /// <param name="type">Type of a property which the projection is bound to</param>
        /// <param name="bindingPath">Path to the property in a <see cref="source"/></param>
        /// <returns><see cref="FrameworkElement"/> representing the value-based projection</returns>
        public static FrameworkElement InitializeValueProjection<T>(
            IValueProjectionHint<T> projectionHint, object source, Type type, string bindingPath = "Value")
            where T : FrameworkElement, new()
        {
            var projection = InitializeProjection<T>(projectionHint, type);

            // Executed default action of any framework element to configure the projection
            var defaultFrameworkElementConfiguration = GetDefaultFrameworkElementConfiguration<T>();
            if (defaultFrameworkElementConfiguration != null)
            {
                defaultFrameworkElementConfiguration(projection);
            }

            if (projectionHint.ValueProperty != null)
            {
                var binding = new Binding(bindingPath)
                                  {
                                      Source = source,
                                      Mode = BindingMode.TwoWay,
                                      StringFormat = null,
                                      ValidatesOnDataErrors = true,
                                      NotifyOnValidationError = true,
                                      
                                      UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
                                  };
                SetValueConverter(binding, type);
                
                projection.SetBinding(projectionHint.ValueProperty, binding);
            }

            // Projection must be wrapped in the AdornerDecorator, otherwise the binding validation 
            // doesn't get updated: 
            // http://karlshifflett.wordpress.com/2008/02/19/wpf-validation-errors-disappear-inside-tabcontrol-when-switching-tabitems/
            var adornerDecorator = new AdornerDecorator { Child = projection };
            return adornerDecorator;
        }

        #region TextBox

        /// <summary>
        /// Gets the TextBox projection. Projection in which the bound object is represented as a <see cref="TextBox"/>
        /// </summary>
        /// <param name="source">Source object</param>
        /// <param name="propertyType">Type of the bound object's property</param>
        /// <param name="propertyName">Name of the boudn object's property</param>
        /// <param name="customConfiguration">Custom configuration applied to the <see cref="TextBox"/></param>
        /// <returns>TextBox projection</returns>
        public static IProjection GetTextBoxProjection(object source, Type propertyType, string propertyName, Action<TextBox> customConfiguration = null)
        {
            var projectionHint = new ValueProjectionHint<TextBox>(
                TextBox.TextProperty,
                (txt, type) =>
                    {
                        txt.BorderThickness = new Thickness(0);
                        txt.Background = new SolidColorBrush(Colors.Transparent);
                        txt.MinWidth = 10;
                    },
                customConfiguration);

            return new Projection() { View = InitializeValueProjection(projectionHint, source, propertyType, propertyName)};
        }

        /// <summary>
        /// Gets the TextBox projection. Projection in which the bound object is represented as a <see cref="TextBox"/>
        /// </summary>
        /// <typeparam name="T">Type of the projection</typeparam>
        /// <param name="source">Source object</param>
        /// <param name="memberExpression">Member that leads to a specific property in a <see cref="source"/></param>
        /// <param name="customConfiguration">Custom configuration applied to the <see cref="T"/></param>
        /// <returns>TextBox projection</returns>
        public static IProjection GetTextBoxProjection<T>(object source, Expression<Func<T>> memberExpression, Action<TextBox> customConfiguration = null)
        {
            var member = memberExpression.GetMember();
            return GetTextBoxProjection(source, member.Type, member.Name, customConfiguration);
        }

        #endregion

        #region NumericTextBox

        /// <summary>
        /// Gets a numeric textBox projection. Projection in which the bound object is represented as a
        /// textBox which allows just numeric charaters inside.
        /// </summary>
        /// <param name="source">Source object</param>
        /// <param name="propertyType">Type of the property</param>
        /// <param name="propertyName">Name of the property</param>
        /// <param name="customConfiguration">Custom configuration applied to the <see cref="NumericTextBoxUserControl"/></param>
        /// <returns>NumericTextBox projection</returns>
        public static IProjection GetNumericTextBoxProjection(object source, Type propertyType, string propertyName, Action<NumericTextBoxUserControl> customConfiguration = null) {
            var projectionHint = new ValueProjectionHint<NumericTextBoxUserControl>(
                TextBox.TextProperty,
                (txt, dataType) => {
                    txt.BorderThickness = new Thickness(0);
                    txt.Background = new SolidColorBrush(Colors.Transparent);

                    var type = dataType.GetTypeOrNullableUnderlyingType();

                    if (type == typeof(decimal) || type == typeof(double) || type == typeof(float)) {
                        txt.Mask = MaskType.Decimal;
                    } else {
                        txt.Mask = MaskType.Integer;
                    }
                },
                customConfiguration);

            return new Common.Projection() { View = InitializeValueProjection(projectionHint, source, propertyType, propertyName) };
        }

        /// <summary>
        /// Gets the numeric textBox projection. Projection in which the bound object is represented as a 
        /// textBox which allows just numeric characters inside.
        /// </summary>
        /// <typeparam name="T">Type of the projection</typeparam>
        /// <param name="source">Source object</param>
        /// <param name="memberExpression">Member that leads to a specific property in the <see cref="source"/></param>
        /// <param name="customConfiguration">Custom configuration applied to the <see cref="NumericTextBoxUserControl"/></param>
        /// <returns></returns>
        public static IProjection GetNumericTextBoxProjection<T>(object source, Expression<Func<T>> memberExpression, Action<NumericTextBoxUserControl> customConfiguration = null)
        {
            var member = memberExpression.GetMember();
            return GetNumericTextBoxProjection(source, member.Type, member.Name, customConfiguration);
        }

        #endregion

        #region ComboBox

        /// <summary>
        /// Gets comboBox projection. Projection in which the bound object is represented as a <see cref="ComboBox"/>
        /// </summary>
        /// <param name="source">Source object</param>
        /// <param name="propertyType">Type of the object's property</param>
        /// <param name="propertyName">Name of the object's property</param>
        /// <param name="itemsSource">Items source of the items in comboBox</param>
        /// <param name="customConfiguration">Custom configuration applied to the <see cref="ComboBox"/></param>
        /// <returns>ComboBox projection</returns>
        public static IProjection GetComboBoxProjection(object source, Type propertyType, string propertyName, IEnumerable itemsSource, Action<ComboBox> customConfiguration = null) {
            var projectionHint = new ValueProjectionHint<ComboBox>(
                Selector.SelectedValueProperty,
                (cbbox, type) => {
                    cbbox.Background = new SolidColorBrush(Colors.Transparent);
                    cbbox.ItemsSource = itemsSource;
                },
                customConfiguration);

            return new Common.Projection() { View = InitializeValueProjection(projectionHint, source, propertyType, propertyName) };
        }

        /// <summary>
        /// Gets comboBox projection. Projection in which the bound object is represented as a <see cref="ComboBox"/>
        /// </summary>
        /// <typeparam name="T">Type of the projection</typeparam>
        /// <param name="source">Source object</param>
        /// <param name="memberExpression">Member expression that leads to a specific property in the <see cref="source"/></param>
        /// <param name="itemsSource">Items source of the items in comboBox</param>
        /// <param name="customConfiguration">Custom configuration applied to the <see cref="ComboBox"/></param>
        /// <returns>ComboBox projection</returns>
        public static IProjection GetComboBoxProjection<T>(object source, Expression<Func<T>> memberExpression, IEnumerable itemsSource, Action<ComboBox> customConfiguration = null) {
            var member = memberExpression.GetMember();
            return GetComboBoxProjection(source, member.Type, member.Name, itemsSource, customConfiguration);
        }

        #endregion

        #region Label

        /// <summary>
        /// Gets the label projection. A projection in which the bound object is represented as a <see cref="Label"/>
        /// </summary>
        /// <param name="source">Source object</param>
        /// <param name="propertyType">Type of the objects' property</param>
        /// <param name="propertyName">Name of the objects' property</param>
        /// <param name="customConfiguration">Custom configuration applied to the <see cref="ComboBox"/></param>
        /// <returns>ComboBox projection</returns>
        public static IProjection GetLabelProjection(object source, Type propertyType, string propertyName, Action<Label> customConfiguration = null)
        {
            var projectionHint = new ValueProjectionHint<Label>(
                Label.ContentProperty, 
                null,
                customConfiguration);
            return new Projection() { View = InitializeValueProjection(projectionHint, source, propertyType, propertyName) };
        }

        /// <summary>
        /// Gets the label projection. A projeciton in which the bound object is represented as a <see cref="Label"/>
        /// </summary>
        /// <typeparam name="T">Type of the projection</typeparam>
        /// <param name="source">Source object</param>
        /// <param name="exprMemberExpression">Member that leads to a specific property in a <see cref="source"/></param>
        /// <param name="customConfiguration">Custom configuration applied to the <see cref="Label"/></param>
        /// <returns>Label projection</returns>
        public static IProjection GetLabelProjection<T>(object source, Expression<Func<T>> exprMemberExpression, Action<Label>customConfiguration = null)
        {
            var member = exprMemberExpression.GetMember();
            return GetLabelProjection(source, member.Type, member.Name, customConfiguration);
        }

        #endregion

        /// <summary>
        /// Set the value converter of the binding to a specific type
        /// </summary>
        /// <param name="binding">Binding to be set</param>
        /// <param name="type">Required type of the value converter</param>
        private static void SetValueConverter(Binding binding, Type type)
        {
            var converter = new ChangeTypeConverter { SourceType = type };
            binding.Converter = converter;
        }
    }
}
